package com.fast.fast.common.gateway.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fast.fast.common.base.constant.CacheConstants;
import com.fast.fast.common.base.exception.BizException;
import com.fast.fast.common.base.page.PageResult;
import com.fast.fast.common.base.vo.Result;
import com.fast.fast.common.gateway.entity.SysUser;
import com.fast.fast.common.gateway.enums.UserStatusEnum;
import com.fast.fast.common.gateway.service.LoginService;
import com.fast.fast.common.gateway.service.SysPermService;
import com.fast.fast.common.gateway.service.SysUserService;
import com.fast.fast.common.redis.util.RedisUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 登录接口实现类
 *
 * @author lyf
 * @date 2022/01/01 00:00 周六
 **/
@Slf4j
@RequiredArgsConstructor
@Service
public class LoginServiceImpl implements LoginService {

    private final SysUserService sysUserService;

    private final RedisUtils redisUtils;

    private final SysPermService sysPermService;

    @SneakyThrows
    @Override
    public Result<Map<String, Object>> login(SysUser sysUser) {
        if (ObjectUtil.isEmpty(sysUser.getUsername())) {
            throw new BizException("用户名不能为空");
        }
        if (ObjectUtil.isEmpty(sysUser.getPassword())) {
            throw new BizException("密码不能为空");
        }
        if (ObjectUtil.isEmpty(sysUser.getVerifyCode())) {
            throw new BizException("验证码不能为空");
        }

        // 记住我
        // 为true时，cookie时效为全局配置的timeout值，关闭浏览器再打开主页无需再次登录
        // 为false时，cookie时效为-1，关闭浏览器会删除cookie，再打开主页需要再次登录
        Boolean rememberMe = false;
        if (ObjectUtil.isNotEmpty(sysUser.getRememberMe())) {
            rememberMe = true;
        }

        String verifyCodeRedisKey = CacheConstants.GATEWAY_LOGIN_VERIFY_CODE_PREFIX + sysUser.getUuid();

        // 获取redis中的验证码
        String verifyCode = (String) redisUtils.get(verifyCodeRedisKey);
        // 与传入的对比
        if (!sysUser.getVerifyCode().equals(verifyCode)) {
            return Result.error("验证码错误");
        }
        // md5加密
        sysUser.setPassword(SecureUtil.md5(sysUser.getPassword()));
        // 登录逻辑
        SysUser old = sysUserService.getOne(Wrappers.lambdaQuery(SysUser.class)
                .eq(SysUser::getUsername, sysUser.getUsername()));
        if (old == null) {
            return Result.error("该用户不存在");
        }
        if (UserStatusEnum.DISABLE.getCode().equals(old.getStatus())) {
            return Result.error("该用户已被停用");
        }
        Result<SysUser> sysUserResult = sysUserService.getByUsernameAndPwd(sysUser);
        if (ObjectUtil.isEmpty(sysUserResult.getData())) {
            return Result.error(sysUserResult.getMsg());
        }
        sysUser = sysUserResult.getData();
        // 登录
        StpUtil.login(sysUser.getId(), rememberMe);
        // 删除redis中的验证码
        redisUtils.delete(verifyCodeRedisKey);
        // 新增登录用户缓存，60秒之后删除该缓存
        this.addCacheOfLoginUser(sysUser);

        Map<String, Object> map = new HashMap<>(16);
        map.put("sysUser", sysUser);
        map.put("username", sysUser.getUsername());
        map.put("deptName", sysUser.getDeptName());
        map.put("postNames", sysUser.getPostNames());
        map.put("userId", sysUser.getId());

        map.put("roles", StpUtil.getRoleList());
        map.put("perms", StpUtil.getPermissionList());
        map.put("permObjs", sysPermService.listPermObjsByLoginId(Long.parseLong(StpUtil.getLoginId().toString())));
        map.put("tokenInfo", StpUtil.getTokenInfo());
        return Result.ok(map);
    }

    @Override
    public Result<?> logout() {
        Object loginId = StpUtil.getLoginId();
        StpUtil.logout();
        // 当所有相同用户都退出时，才删除该用户的相关信息
        Boolean hasSysUser = redisUtils.hasKey(CacheConstants.SATOKEN_LOGIN_SESSION + loginId);
        if (!hasSysUser) {
            // 删除redis中登录用户的信息
            redisUtils.delete(CacheConstants.GATEWAY_LOGIN_SYS_USER_USERID_PREFIX + loginId);
            // 删除redis中登录用户的角色编码集合
            redisUtils.delete(CacheConstants.GATEWAY_LOGIN_SYS_ROLE_CODE_USERID + "::" + loginId);
            // 删除redis中登录用户的权限编码集合
            redisUtils.delete(CacheConstants.GATEWAY_LOGIN_SYS_PERM_CODE_USERID + "::" + loginId);
            // 删除redis中登录用户的权限对象集合
            redisUtils.delete(CacheConstants.GATEWAY_LOGIN_SYS_PERM_USERID + "::" + loginId);
        }
        return Result.ok();
    }

    @Override
    public Result<?> getPermAndRole() {
        Object loginId = StpUtil.getLoginId();
        Object perms = StpUtil.getPermissionList();
        Object permObjs = sysPermService.listPermObjsByLoginId(Long.parseLong(loginId.toString()));
        Object roles = StpUtil.getRoleList();

        // 获取登录用户缓存
        SysUser sysUser = BeanUtil.copyProperties(redisUtils.get(CacheConstants.GATEWAY_LOGIN_SYS_USER_USERID_PREFIX + loginId), SysUser.class);
        // 当60秒内没有请求该接口，缓存sysUser会过期，视为离线
        if (ObjectUtil.isEmpty(sysUser)) {
            sysUser = sysUserService.getById(Long.valueOf(loginId.toString()));
        }
        // 新增登录用户缓存，60秒之后删除该缓存
        this.addCacheOfLoginUser(sysUser);

        JSONObject rs = new JSONObject(true);
        rs.put("perms", perms);
        rs.put("permObjs", permObjs);
        rs.put("roles", roles);
        return Result.ok(rs);
    }

    @Override
    public Result<?> refreshPermAndRole() {
        Object loginId = StpUtil.getLoginId();
        Object perms = redisUtils.get(CacheConstants.GATEWAY_LOGIN_SYS_PERM_CODE_USERID + "::" + loginId);
        Object permObjs = redisUtils.get(CacheConstants.GATEWAY_LOGIN_SYS_PERM_USERID + "::" + loginId);
        Object roles = redisUtils.get(CacheConstants.GATEWAY_LOGIN_SYS_ROLE_CODE_USERID + "::" + loginId);

        // 获取登录用户缓存
        SysUser sysUser = BeanUtil.copyProperties(redisUtils.get(CacheConstants.GATEWAY_LOGIN_SYS_USER_USERID_PREFIX + loginId), SysUser.class);
        // 当60秒内没有请求该接口，缓存sysUser会过期，视为离线
        if (ObjectUtil.isEmpty(sysUser)) {
            sysUser = sysUserService.getById(Long.valueOf(loginId.toString()));
        }
        // 新增登录用户缓存，60秒之后删除该缓存
        this.addCacheOfLoginUser(sysUser);

        // 是否需要刷新页面的标识
        boolean needRefreshPage = perms == null || permObjs == null || roles == null;
        return Result.ok(needRefreshPage);
    }

    @Override
    public Result<?> listOnlineUsers(SysUser sysUser) {
        // 从数据库中分页查询用户集合
        IPage<SysUser> page = sysUserService.page(
                new Page<>(sysUser.getCurrent(), sysUser.getSize())
                , Wrappers.lambdaQuery(SysUser.class)
                        .orderByAsc(SysUser::getUsername)
        );
        PageResult pageResult = PageResult.of(page);
        List<SysUser> pageSysUsers = pageResult.getRecords();
        if (ObjectUtil.isEmpty(pageSysUsers)) {
            return Result.ok(CollUtil.newArrayList());
        }
        // 从redis中获取当前在线用户集合
        List<SysUser> onlineUsers = BeanUtil.copyToList(redisUtils.multiGet(CacheConstants.GATEWAY_LOGIN_SYS_USER_USERID_PREFIX), SysUser.class);

        // 在线用户数
        Integer onlineCounts = 0;
        // 总登录数
        Integer allConcurrentCounts = 0;
        for (SysUser pageSysUser : pageSysUsers) {
            for (SysUser onlineUser : onlineUsers) {
                if (pageSysUser.getId().equals(onlineUser.getId())) {
                    pageSysUser.setOnline(true);
                    // 设置该账号的同时登录数集合
                    if (ObjectUtil.isNotEmpty(onlineUser.getTokenSignList())) {
                        pageSysUser.setTokenSignList(onlineUser.getTokenSignList());
                        pageSysUser.setConcurrentCounts(onlineUser.getTokenSignList().size());
                    }
                }
            }
        }
        List<SysUser> allSysUsers = sysUserService.list(Wrappers.lambdaQuery(SysUser.class)
                .orderByAsc(SysUser::getUsername)
        );
        for (SysUser allSysUser : allSysUsers) {
            for (SysUser onlineUser : onlineUsers) {
                if (allSysUser.getId().equals(onlineUser.getId())) {
                    allSysUser.setOnline(true);
                    // 设置在线用户数
                    onlineCounts++;
                    // 设置该账号的同时登录数集合
                    if (ObjectUtil.isNotEmpty(onlineUser.getTokenSignList())) {
                        allSysUser.setTokenSignList(onlineUser.getTokenSignList());
                        allConcurrentCounts += allSysUser.getTokenSignList().size();
                    }
                }
            }
        }
        JSONObject rs = new JSONObject(true);
        rs.put("pageResult", pageResult);
        rs.put("onlineCounts", onlineCounts);
        rs.put("allConcurrentCounts", allConcurrentCounts);
        return Result.ok(rs);
    }

    /**
     * 新增登录用户缓存，60秒之后删除该缓存
     *
     * @param sysUser 登录用户
     * @return
     */
    private boolean addCacheOfLoginUser(SysUser sysUser) {
        if (ObjectUtil.isEmpty(sysUser)) {
            return false;
        }
        sysUser.setOnline(true);
        // 从redis中获取该账号的同时登录数集合
        JSONObject jo = JSONObject.parseObject(JSONObject.toJSONString(redisUtils.get(CacheConstants.SATOKEN_LOGIN_SESSION + sysUser.getId())));
        JSONArray tokenSignList = jo.getJSONArray("tokenSignList");
        if (ObjectUtil.isNotEmpty(tokenSignList)) {
            sysUser.setTokenSignList(tokenSignList);
        }
        ThreadUtil.safeSleep(500);
        // 缓存登录用户信息到redis，必须把对象序列化成json字符串，否则可能会因为该对象的类全限定名不同而导致反序列化失败
        redisUtils.set(CacheConstants.GATEWAY_LOGIN_SYS_USER_USERID_PREFIX + sysUser.getId()
                , JSONObject.parseObject(JSONObject.toJSONString(sysUser))
                , Duration.ofSeconds(60).getSeconds()
        );
        return true;
    }

}




